package cn.com.jcoo.security;

import cn.com.jcoo.security.enums.SignAlgorithm;
import lombok.SneakyThrows;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAKeyGenParameterSpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

/**
 * RSA 加解密工具类：基于BouncyCastle库（BC）
 * <p>
 * RSA算法——由三位发明者Ronald Rivest、Adi Shamir 和 Leonard Adleman 姓氏的首字母拼在一起组成。
 * RSA算法属于“公开密钥加密技术”，其加密和解密的秘钥不同。 用于加密的密钥可以公开，因此称为“公钥”，而用于解密的密钥是只有自己才知道，称为“私钥”。
 * 当采用公钥加密时，使用私钥解密；采用私钥加密时，使用公钥解密
 * </p>
 *
 * @author jintingying
 * @since 1.0
 */
@SuppressWarnings("unused")
public class RasUtils {
	private static final String RSA_ALGORITHM = "RSA";

	private static final String DEFAULT_RSA_ALGORITHM_MODE = "RSA/ECB/PKCS1Padding";

	public static final String KEY_PUBLIC = "PUBLIC";

	public static final String KEY_PRIVATE = "PRIVATE";


	// private static final int MAX_ENCRYPT_BLOCK = 245;
	// /**
	// * RSA最大解密密文大小
	// */
	// private static final int MAX_DECRYPT_BLOCK = 256;
	/**
	 * RSA 位数 如果采用2048 上面最大加密和最大解密则须填写: 245 256
	 */
	private static final int INITIALIZE_LENGTH = 2048;

	static {
		// 使用BouncyCastle库提供的加解密服务
		if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
			Security.addProvider(new BouncyCastleProvider());
		}
	}

	/**
	 * 生成密钥对(Base64编码格式)
	 */
	public static Map<String, String> generatorRsaKeyPair()
			throws InvalidAlgorithmParameterException, NoSuchAlgorithmException {
		AlgorithmParameterSpec parameterSpec = new RSAKeyGenParameterSpec(
				INITIALIZE_LENGTH, BigInteger.valueOf(3L));
		KeyPair rsa = SecurityUtils.generatorKeyPair(RSA_ALGORITHM, parameterSpec);
		Map<String, String> keyMap = new HashMap<>(2);
		keyMap.put(KEY_PRIVATE, Base64.encodeBase64String(rsa.getPrivate().getEncoded()));
		keyMap.put(KEY_PUBLIC, Base64.encodeBase64String(rsa.getPublic().getEncoded()));
		return keyMap;
	}

	/**
	 * 使用私钥进行MD5签名（Base64编码格式）
	 *
	 * @param data 数据
	 * @param privateKey 私钥（Base64编码格式）
	 */
	public static String sign(byte[] data, String privateKey) throws Exception {
		byte[] keyBytes = Base64.decodeBase64(privateKey);
		PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM,
				Security.getProvider(BouncyCastleProvider.PROVIDER_NAME));
		PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
		Signature signature = Signature.getInstance(SignAlgorithm.MD5withRSA.name());
		signature.initSign(privateK);
		signature.update(data);
		return Base64.encodeBase64String(signature.sign());
	}

	/**
	 * 使用公钥验签
	 *
	 * @param data 数据
	 * @param publicKey 公钥（Base64编码格式）
	 * @param sign 私钥签名后的数据
	 */
	public static boolean verifySign(byte[] data, String publicKey, String sign)
			throws Exception {
		byte[] keyBytes = Base64.decodeBase64(publicKey);
		X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM,
				Security.getProvider(BouncyCastleProvider.PROVIDER_NAME));
		PublicKey publicK = keyFactory.generatePublic(keySpec);
		Signature signature = Signature.getInstance(SignAlgorithm.MD5withRSA.name());
		signature.initVerify(publicK);
		signature.update(data);
		return signature.verify(Base64.decodeBase64(sign));
	}

	/**
	 * 自定义分块加解密
	 */
	private static byte[] doFinal(Cipher cipher, byte[] data, int blockSize)
			throws IOException, IllegalBlockSizeException, BadPaddingException {
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		int offSet = 0;
		byte[] buffer;
		int i = 0;
		int inputLen = data.length;
		while (inputLen - offSet > 0) {
			if (inputLen - offSet > blockSize) {
				buffer = cipher.doFinal(data, offSet, blockSize);
			}
			else {
				buffer = cipher.doFinal(data, offSet, inputLen - offSet);
			}
			out.write(buffer, 0, buffer.length);
			i++;
			offSet = i * blockSize;
		}
		byte[] ret = out.toByteArray();
		out.close();
		return ret;
	}

	/* ============公钥加密 私钥解密============= */
	public static String encryptByPublicKey(String data, String key) {
		return encrypt(data, key, true);
	}

	public static String decryptByPrivateKey(String data, String key) {
		return decrypt(data, key, false);
	}

	/* ============私钥加密 公钥解密============= */
	public static String encryptByPrivateKey(String data, String key) {
		return encrypt(data, key, false);
	}

	public static String decryptByPublicKey(String data, String key) {
		return decrypt(data, key, true);
	}

	@SneakyThrows
	private static String encrypt(String data, String key, boolean isPublicKey) {
		byte[] keyBytes = Base64.decodeBase64(key);
		// 将key转化为X509编码（密钥规范）
		KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
		Key keyx;
		if (isPublicKey) {
			X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
			keyx = keyFactory.generatePublic(keySpec);
		}
		else {
			PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
			keyx = keyFactory.generatePrivate(keySpec);
		}
		Cipher cipher = SecurityUtils.generateCipher(DEFAULT_RSA_ALGORITHM_MODE,
				Cipher.ENCRYPT_MODE, keyx);
		byte[] encryptedData = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
		return Base64.encodeBase64String(encryptedData);
	}

	/**
	 * 将公钥key转化为X509编码（密钥规范）
	 */
	public static PublicKey generateSpecPublicKey(byte[] keyBytes) throws Exception {
		KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
		X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
		return keyFactory.generatePublic(keySpec);

	}

	/**
	 * 将私钥key转化为PKCS8编码（密钥规范）
	 */
	public static PrivateKey generateSpecPrivateKey(byte[] keyBytes) throws Exception {
		// 将key转化为PKCS8编码（密钥规范）
		KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
		PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
		return keyFactory.generatePrivate(keySpec);
	}

	private static String decrypt(String data, String key, boolean isPublicKey) {
		try {
			byte[] keyBytes = Base64.decodeBase64(key);
			Key specKey = isPublicKey ? generateSpecPublicKey(keyBytes)
					: generateSpecPrivateKey(keyBytes);
			// 生成解密器
			Cipher cipher = SecurityUtils.generateCipher(DEFAULT_RSA_ALGORITHM_MODE,
					Cipher.DECRYPT_MODE, specKey);
			byte[] dataBytes = Base64.decodeBase64(data);
			byte[] decryptedBytes = cipher.doFinal(dataBytes);
			return new String(decryptedBytes);
		}
		catch (Exception e) {
			throw new RuntimeException(e.getMessage(), e);
		}
	}

	public static void main(String[] args) throws Exception {
		Map<String, String> key = RasUtils.generatorRsaKeyPair();
		String aPublic = key.get(KEY_PUBLIC);
		String aPrivate = key.get(KEY_PRIVATE);
		System.out.println("公钥：" + aPublic);
		System.out.println("私钥：" + aPrivate);
		System.err.println("---------------------------0");
		String text = "_xA1231*&x!321䵵";
		System.out.println("明文：" + text);
		System.err.println("---------------------------1");
		String encryptedData = RasUtils.encryptByPublicKey(text, aPublic);
		System.out.println("公钥加密后：" + encryptedData);
		String sign = RasUtils.sign(encryptedData.getBytes(StandardCharsets.UTF_8),
				aPrivate);
		System.out.println("私钥密文签名：" + sign);
		System.out.println("公钥密文验签：" + RasUtils.verifySign(
				encryptedData.getBytes(StandardCharsets.UTF_8), aPublic, sign));
		String decryptedData = RasUtils.decryptByPrivateKey(encryptedData, aPrivate);
		System.out.println("私钥解密后：" + decryptedData);
		System.err.println("---------------------------2");
		String encryptedData_s = RasUtils.encryptByPrivateKey(text, aPrivate);
		System.out.println("私钥加密后：" + encryptedData_s);
		String decryptedData_s = RasUtils.decryptByPublicKey(encryptedData_s, aPublic);
		System.out.println("公钥解密后：" + decryptedData_s);
		System.err.println("---------------------------3");
		String encryptedData_s2 = RasUtils.encryptByPrivateKey(text, aPrivate);
		System.out.println("私钥加密后：" + encryptedData_s2);
		String decryptedData_s2 = RasUtils.decryptByPublicKey(encryptedData_s2, aPublic);
		System.out.println("公钥解密后：" + decryptedData_s2);
	}
}
